package com.cmes.core.utils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;

/**
 * 数学计算工具类
 *
 * @author Bruce.Gong
 * @since 1.0.0-SNAPSHOT
 */
public class MathUtils {

    /**
     * 如果 {@code number} 为 {@code null}，则返回 {@code 0}，否则返回 {@code number} 本身
     *
     * @param number 数值
     * @return number || 0
     */
    public static BigDecimal orElseZero(BigDecimal number) {
        return NumberUtils.orElse(number, BigDecimal.ZERO);
    }

    /**
     * 如果 {@code number} 为 {@code null}，则返回 {@code 0}，否则返回 {@code number} 本身
     *
     * @param number 数值
     * @return number || 0
     */
    public static Integer orElseZero(Integer number) {
        return number == null ? 0 : number;
    }

    /**
     * 如果 {@code number} 为 {@code null}，则返回 {@code 0}，否则返回 {@code number} 本身
     *
     * @param number 数值
     * @return number || 0
     */
    public static Long orElseZero(Long number) {
        return number == null ? 0L : number;
    }

    /**
     * Long 转 BigDecimal，如果 {@code number} 为 {@code null} 返回 {@code BigDecimal.ZERO}
     *
     * @param number Integer
     * @return BigDecimal
     */
    public static BigDecimal toBigDecimal(Long number) {
        return toBigDecimal(number, BigDecimal.ZERO);
    }

    /**
     * Integer 转 BigDecimal，如果 {@code number} 为 {@code null} 返回 {@code BigDecimal.ZERO}
     *
     * @param number Long
     * @return BigDecimal
     */
    public static BigDecimal toBigDecimal(Integer number) {
        return toBigDecimal(number, BigDecimal.ZERO);
    }

    /**
     * Double 转 BigDecimal，如果 {@code number} 为 {@code null} 返回 {@code BigDecimal.ZERO}
     *
     * @param number Double
     * @return BigDecimal
     */
    public static BigDecimal toBigDecimal(Double number) {
        return toBigDecimal(number, BigDecimal.ZERO);
    }

    /**
     * String 转 BigDecimal，如果 {@code number} 为 {@code null} 返回 {@code BigDecimal.ZERO}
     *
     * @param number String
     * @return BigDecimal
     */
    public static BigDecimal toBigDecimal(String number) {
        return toBigDecimal(number, BigDecimal.ZERO);
    }

    /**
     * Long 转 BigDecimal，如果 {@code number} 为 {@code null} 返回 {@code defaultValue}
     *
     * @param number Integer
     * @return BigDecimal
     */
    public static BigDecimal toBigDecimal(Long number, BigDecimal defaultValue) {
        return number == null ? defaultValue : BigDecimal.valueOf(number);
    }

    /**
     * Integer 转 BigDecimal，如果 {@code number} 为 {@code null} 返回 {@code defaultValue}
     *
     * @param number Long
     * @return BigDecimal
     */
    public static BigDecimal toBigDecimal(Integer number, BigDecimal defaultValue) {
        return number == null ? defaultValue : BigDecimal.valueOf(number);
    }

    /**
     * Double 转 BigDecimal，如果 {@code number} 为 {@code null} 返回 {@code defaultValue}
     *
     * @param number Double
     * @return BigDecimal
     */
    public static BigDecimal toBigDecimal(Double number, BigDecimal defaultValue) {
        return number == null ? defaultValue : BigDecimal.valueOf(number);
    }

    /**
     * Double 转 BigDecimal，如果 {@code number} 为 {@code null} 返回 {@code defaultValue}
     *
     * @param number String
     * @return BigDecimal
     */
    public static BigDecimal toBigDecimal(String number, BigDecimal defaultValue) {
        return StringUtils.isBlank(number) ? defaultValue : new BigDecimal(number);
    }

    /**
     * 判断两个数是否相等，如果某一个为空，则不等
     *
     * @param number1 数1
     * @param number2 数2
     * @return 是否相等
     */
    public static boolean equals(Integer number1, Integer number2) {
        return number1 != null && number2 != null && number1.compareTo(number2) == 0;
    }

    /**
     * 判断两个数是否相等，如果某一个为空，则不等
     *
     * @param number1 数1
     * @param number2 数2
     * @return 是否相等
     */
    public static boolean equals(Long number1, Long number2) {
        return number1 != null && number2 != null && number1.compareTo(number2) == 0;
    }

    /**
     * 判断两个数是否相等，如果某一个为空，则不等
     *
     * @param number1 数1
     * @param number2 数2
     * @return 是否相等
     */
    public static boolean equals(BigDecimal number1, BigDecimal number2) {
        return number1 != null && number2 != null && number1.compareTo(number2) == 0;
    }

    /**
     * 求和
     *
     * @param number1 数1
     * @param number2 数2
     * @return number1 + number2
     */
    public static BigDecimal sum(BigDecimal number1, BigDecimal number2) {
        return orElseZero(number1).add(orElseZero(number2));
    }

    /**
     * 求和
     *
     * @param numbers 多个数组成数组
     * @return 0 + numbers
     */
    public static BigDecimal sum(BigDecimal... numbers) {
        return ArrayUtils.isEmpty(numbers) ? BigDecimal.ZERO : Arrays.stream(numbers).map(MathUtils::orElseZero).reduce(BigDecimal.ZERO, MathUtils::sum);
    }

    /**
     * 求差
     *
     * @param number1 数1，如果为空，则值为 0
     * @param number2 数2，如果为空，则值为 0
     * @return number1 - number2
     */
    public static BigDecimal subtract(BigDecimal number1, BigDecimal number2) {
        return orElseZero(number1).subtract(orElseZero(number2));
    }

    /**
     * 求差
     *
     * @param numbers 数1，如果为空，则值为 0
     * @return number1 - number2
     */
    public static BigDecimal subtract(BigDecimal... numbers) {
        return ArrayUtils.isEmpty(numbers) ? BigDecimal.ZERO : subtract(sum(numbers[0], numbers[0]), sum(numbers));
    }

    /**
     * 求积
     *
     * @param number1 数1
     * @param number2 数2
     * @return number1 * number2
     */
    public static BigDecimal multiply(BigDecimal number1, BigDecimal number2) {
        return orElseZero(number1).multiply(orElseZero(number2));
    }

    /**
     * 求积
     *
     * @param numbers 多个数组成数组
     * @return 1 * numbers
     */
    public static BigDecimal multiply(BigDecimal... numbers) {
        return ArrayUtils.isEmpty(numbers) ? BigDecimal.ZERO : Arrays.stream(numbers).map(MathUtils::orElseZero).reduce(BigDecimal.ONE, MathUtils::multiply);
    }

    /**
     * 两个数相除，默认保留两位小数，向上取整
     *
     * <pre>
     *  divide(null, null)    = 0
     *  divide(10, null)      = 0
     *  divide(null, 10)      = 0
     *  divide(10, 0)         = 0
     *  divide(10, 2)         = 5
     * </pre>
     *
     * @param number1 数1
     * @param number2 数2
     * @return number1 / number2
     */
    public static BigDecimal divide(BigDecimal number1, BigDecimal number2) {
        return divide(number1, number2, 2);
    }

    /**
     * 两个数相除，向上取整
     *
     * @param number1 数1
     * @param number2 数2
     * @param scale   保留小数位数
     * @return number1 / number2
     */
    public static BigDecimal divide(BigDecimal number1, BigDecimal number2, int scale) {
        return divide(number1, number2, scale, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 两个数相除
     *
     * @param number1 数1
     * @param number2 数2
     * @param scale   保留小数位数
     * @param round   取整策略，例如 {@code BigDecimal.ROUND_HALF_UP}
     * @return number1 / number2
     */
    public static BigDecimal divide(BigDecimal number1, BigDecimal number2, int scale, int round) {
        if (number1 == null || number2 == null || NumberUtils.equals(number2, BigDecimal.ZERO)) {
            return BigDecimal.ZERO;
        }
        return number1.divide(number2, scale, round);
    }

    /**
     * 两个数相除
     *
     * @param number1 数1
     * @param number2 数2
     * @param scale   保留小数位数
     * @param round   取整策略，例如 {@code BigDecimal.ROUND_HALF_UP}
     * @return number1 / number2
     */
    public static BigDecimal divide(BigDecimal number1, BigDecimal number2, int scale, RoundingMode round) {
        if (number1 == null || number2 == null || NumberUtils.equals(number2, BigDecimal.ZERO)) {
            return BigDecimal.ZERO;
        }
        return number1.divide(number2, scale, round);
    }

    /**
     * 保留小数位，默认使用四舍五入
     *
     * @param number 数值
     * @param scale  保留小数位
     * @return BigDecimal
     */
    public static BigDecimal round(BigDecimal number, int scale) {
        return round(number, scale, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 保留小数位，默认使用四舍五入
     *
     * @param number 数值
     * @param scale  保留小数位
     * @param round  取整策略，例如 {@code BigDecimal.ROUND_HALF_UP}
     * @return BigDecimal
     */
    public static BigDecimal round(BigDecimal number, int scale, int round) {
        return number == null ? null : number.setScale(scale, round);
    }

    /**
     * 判断是否为 0，如果 number == null，返回 false
     *
     * @param number 数值
     * @return true | false
     */
    public static boolean eqZero(Integer number) {
        return eqZero(number, false);
    }

    /**
     * 判断是否为 0
     *
     * @param number      数值
     * @param convertNull 如果 number 为 null，是否转换为 0
     * @return true | false
     */
    public static boolean eqZero(Integer number, boolean convertNull) {
        number = convertNull ? orElseZero(number) : number;
        return number != null && number.compareTo(0) == 0;
    }

    /**
     * 判断是否为 0，如果 number == null，返回 false
     *
     * @param number 数值
     * @return true | false
     */
    public static boolean eqZero(Long number) {
        return eqZero(number, false);
    }

    /**
     * 判断是否为 0
     *
     * @param number      数值
     * @param convertNull 如果 number 为 null，是否转换为 0
     * @return true | false
     */
    public static boolean eqZero(Long number, boolean convertNull) {
        number = convertNull ? orElseZero(number) : number;
        return number != null && number.compareTo(0L) == 0;
    }

    /**
     * 判断是否为 0，如果 number == null，返回 false
     *
     * @param number 数值
     * @return true | false
     */
    public static boolean eqZero(BigDecimal number) {
        return eqZero(number, false);
    }

    /**
     * 判断是否为 0
     *
     * @param number      数值
     * @param convertNull 如果 number 为 null，是否转换为 0
     * @return true | false
     */
    public static boolean eqZero(BigDecimal number, boolean convertNull) {
        number = convertNull ? orElseZero(number) : number;
        return number != null && number.equals(BigDecimal.ZERO);
    }

    /**
     * number1 > number2 ? true : false
     * <p>
     * 如果数值为 null，则置为 0
     *
     * <pre>
     *  gt(null, null)    = false
     *  gt(0, null)       = false
     *  gt(1, null)       = true
     *  gt(1, 0)          = true
     * </pre>
     *
     * @param number1 数1
     * @param number2 数2
     * @return true | false
     */
    public static boolean gt(BigDecimal number1, BigDecimal number2) {
        return orElseZero(number1).compareTo(orElseZero(number2)) > 0;
    }

    /**
     * number > 0 ? true : false
     * <p>
     * 如果数值为 null，则置为 0
     *
     * <pre>
     *  gtZero(null)    = false
     *  gtZero(0)       = false
     *  gtZero(1)       = true
     * </pre>
     *
     * @param number 数1
     * @return true | false
     */
    public static boolean gtZero(BigDecimal number) {
        return gt(number, BigDecimal.ZERO);
    }

    /**
     * number1 >= number2 ? true : false
     * <p>
     * 如果数值为 null，则置为 0
     *
     * <pre>
     *  gt(null, null)    = true
     *  gt(0, null)       = true
     *  gt(1, null)       = true
     *  gt(1, 0)          = true
     * </pre>
     *
     * @param number1 数1
     * @param number2 数2
     * @return true | false
     */
    public static boolean egt(BigDecimal number1, BigDecimal number2) {
        return orElseZero(number1).compareTo(orElseZero(number2)) >= 0;
    }

    /**
     * number >= 0 ? true : false
     * <p>
     * 如果数值为 null，则置为 0
     *
     * @param number 数1
     * @return true | false
     */
    public static boolean egtZero(BigDecimal number) {
        return egt(number, BigDecimal.ZERO);
    }

    /**
     * number1 < number2 ? true : false
     * <p>
     * 如果数值为 null，则置为 0
     *
     * @param number1 数1
     * @param number2 数2
     * @return true | false
     */
    public static boolean lt(BigDecimal number1, BigDecimal number2) {
        return orElseZero(number1).compareTo(orElseZero(number2)) < 0;
    }

    /**
     * number < 0 ? true : false
     * <p>
     * 如果数值为 null，则置为 0
     *
     * @param number 数1
     * @return true | false
     */
    public static boolean ltZero(BigDecimal number) {
        return lt(number, BigDecimal.ZERO);
    }

    /**
     * number1 <= number2 ? true : false
     * <p>
     * 如果数值为 null，则置为 0
     *
     * @param number1 数1
     * @param number2 数2
     * @return true | false
     */
    public static boolean elt(BigDecimal number1, BigDecimal number2) {
        return orElseZero(number1).compareTo(orElseZero(number2)) <= 0;
    }

    /**
     * number <= 0 ? true : false
     * <p>
     * 如果数值为 null，则置为 0
     *
     * @param number 数1
     * @return true | false
     */
    public static boolean eltZero(BigDecimal number) {
        return elt(number, BigDecimal.ZERO);
    }

    /**
     * 判断最大值，不为 null 的值永远大于 null 值
     *
     * @param number1 数1
     * @param number2 数2
     * @return max
     */
    public static BigDecimal max(BigDecimal number1, BigDecimal number2) {
        if (number1 == null) {
            return number2;
        } else if (number2 == null) {
            return number1;
        } else {
            return number1.compareTo(number2) >= 0 ? number1 : number2;
        }
    }

    /**
     * 求和
     *
     * @param numbers 多个数组成数组
     * @return 0 + numbers
     */
    public static BigDecimal sum(Double... numbers) {
        return ArrayUtils.isEmpty(numbers) ? BigDecimal.ZERO : Arrays.stream(numbers).map(MathUtils::toBigDecimal).reduce(BigDecimal.ZERO, MathUtils::sum);
    }

    /**
     * 求差
     *
     * @param number1 数1，如果为空，则值为 0
     * @param number2 数2，如果为空，则值为 0
     * @return number1 - number2
     */
    public static BigDecimal subtract(Double number1, Double number2) {
        return subtract(toBigDecimal(number1), toBigDecimal(number2));
    }

    /**
     * 求积
     *
     * @param numbers 多个数组成数组
     * @return 1 * numbers
     */
    public static BigDecimal multiply(Double... numbers) {
        return ArrayUtils.isEmpty(numbers) ? BigDecimal.ZERO : Arrays.stream(numbers).map(MathUtils::toBigDecimal).reduce(BigDecimal.ONE, MathUtils::multiply);
    }

    /**
     * 两个数相除，默认保留两位小数，向上取整
     *
     * <pre>
     *  divide(null, null)    = 0
     *  divide(10, null)      = 0
     *  divide(null, 10)      = 0
     *  divide(10, 0)         = 0
     *  divide(10, 2)         = 5
     * </pre>
     *
     * @param number1 数1
     * @param number2 数2
     * @return number1 / number2
     */
    public static BigDecimal divide(Double number1, Double number2) {
        return divide(number1, number2, 2);
    }

    /**
     * 两个数相除，向上取整
     *
     * @param number1 数1
     * @param number2 数2
     * @param scale   保留小数位数
     * @return number1 / number2
     */
    public static BigDecimal divide(Double number1, Double number2, int scale) {
        return divide(number1, number2, scale, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 两个数相除
     *
     * @param number1 数1
     * @param number2 数2
     * @param scale   保留小数位数
     * @param round   取整策略，例如 {@code BigDecimal.ROUND_HALF_UP}
     * @return number1 / number2
     */
    public static BigDecimal divide(Double number1, Double number2, int scale, int round) {
        if (number1 == null || number2 == null) {
            return BigDecimal.ZERO;
        }
        return divide(toBigDecimal(number1), toBigDecimal(number2), scale, round);
    }

    /**
     * 两个数相除
     *
     * @param number1 数1
     * @param number2 数2
     * @param scale   保留小数位数
     * @param round   取整策略，例如 {@code BigDecimal.ROUND_HALF_UP}
     * @return number1 / number2
     */
    public static BigDecimal divide(Double number1, Double number2, int scale, RoundingMode round) {
        if (number1 == null || number2 == null) {
            return BigDecimal.ZERO;
        }
        return divide(toBigDecimal(number1), toBigDecimal(number2), scale, round);
    }

    /**
     * 保留小数位，默认使用四舍五入
     *
     * @param number 数值
     * @param scale  保留小数位
     * @return BigDecimal
     */
    public static Double round(Double number, int scale) {
        return round(number, scale, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 保留小数位，默认使用四舍五入
     *
     * @param number 数值
     * @param scale  保留小数位
     * @return BigDecimal
     */
    public static Double round(Double number, int scale, int round) {
        return number == null ? null : toBigDecimal(number).setScale(scale, round).doubleValue();
    }

    /**
     * 计算最小值
     *
     * @param bds 数值
     * @return BigDecimal
     */
    public static BigDecimal min(BigDecimal... bds) {
        return Arrays.stream(bds).min(BigDecimal::compareTo).orElse(null);
    }

    /**
     * 计算最大值
     *
     * @param bds 数值
     * @return BigDecimal
     */
    public static BigDecimal max(BigDecimal... bds) {
        return Arrays.stream(bds).max(BigDecimal::compareTo).orElse(null);
    }

}